<?php

class DB_Pdo extends DB_Db {

    protected $_table;
    protected $_connect = false;
    private $_config;
    private $_wdsn;
    private $_wuser;
    private $_wpassword;
    private $_wlink;
    private $_rdsn;
    private $_ruser;
    private $_rpassword;
    private $_rlink;
    private $_params = array();
    private $_pdo_param;
    private $_prefix;
    private $table;
    private $query = array();

    public function __construct() {
        $this->_config = Yaf_Registry::get('config')->database->mysql->toArray();
        /** 写配置 */
        $this->_wdsn = 'mysql:host=' . $this->_config['write']['host'] . ';dbname=' . $this->_config['write']['dbname'] . ';port=' . $this->_config['write']['port'] . ';charset=' . $this->_config['write']['charset'];
        $this->_wuser = $this->_config['write']['user'];
        $this->_wpassword = $this->_config['write']['password'];
        /** 读配置 */
        $this->_rdsn = 'mysql:host=' . $this->_config['read']['host'] . ';dbname=' . $this->_config['read']['dbname'] . ';port=' . $this->_config['read']['port'] . ';charset=' . $this->_config['read']['charset'];
        $this->_ruser = $this->_config['read']['user'];
        $this->_rpassword = $this->_config['read']['password'];

        $this->_pdo_param = array(
            PDO::FETCH_ASSOC => TRUE,
        );
        $this->_prefix = isset($this->_config['prefix']) ? $this->_config['prefix'] : '';
        $this->table($this->_table);
    }

    protected function getRInstance() {
        try {
            if (!$this->_rlink instanceof self) {
                $this->_rlink = new PDO($this->_rdsn, $this->_ruser, $this->_rpassword, $this->_pdo_param);
            }
            return $this->_rlink;
        } catch (Yaf_Exception $e) {
            LogTool::mysqlerrorLog()->log($e->getMessage());
            return false;
        }
    }

    protected function getWInstance() {
        try {
            if (!$this->_wlink instanceof self) {
                $this->_wlink = new PDO($this->_wdsn, $this->_wuser, $this->_wpassword, $this->_pdo_param);
            }
            return $this->_wlink;
        } catch (Yaf_Exception $e) {
            LogTool::mysqlerrorLog()->log($e->getMessage());
            return false;
        }
    }

    protected function disconnect() {
        $this->_wlink = null;
        $this->_rlink = null;
        self::$_link = null;
    }

    public function table($table, $alias = '') {
        $this->table = $this->_prefix . $table;
        if ($alias)
            $this->query['alias'] = $alias; //别名
        return $this;
    }

    public function select() {
        $fields = func_get_args();
        $field = current($fields);
        $field = is_array($field) ? $field : ($fields ? : array('*'));
        $this->query['select'] = $field;
        return $this;
    }

    public function distinct() {
        $this->query['distinct'] = 1;
        return $this;
    }

    public function count($distinct_needle = FALSE, $fields = '*') {
        $this->query['select'][] = 'COUNT(' . ($distinct_needle ? 'DISTINCT ' : '') . ($fields != '*' ? '`' . $fields . '`' : '*') . ') AS COUNT ';
        return $this;
    }

    public function sum($fields = '*', $distinct_needle = FALSE) {
        $this->query['select'][] = 'SUM(' . ($distinct_needle ? 'DISTINCT ' : '') . ($fields != '*' ? '`' . $fields . '`' : '*') . ') AS SUM ';
        return $this;
    }

    public function avg($fields = '*', $distinct_needle = FALSE) {
        $this->query['select'][] = 'AVG(' . ($distinct_needle ? 'DISTINCT ' : '') . ($fields != '*' ? '`' . $fields . '`' : '*') . ') AS AVG ';
        return $this;
    }

    public function insert(array $fields) {
        if (count($fields)) {
            $this->query['insertOne'] = array_keys($fields);
            $this->_params = array_values($fields);
        } else {
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'insert error');
        }
        return $this;
    }

    public function insertMulti(array $fields) {
        if (count($fields)) {
            $this->query['insertMulti'] = array_keys(current($fields));
            foreach ($fields as $field) {
                $this->_params = array_merge($this->_params, array_values($field));
            }
        } else {
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'insert error');
        }
        return $this;
    }

    public function update(array $fields) {
        if (count($fields)) {
            foreach ($fields as $key => $value) {
                $bind_key = ':' . $key . '_' . uniqid();

                $this->query['update'][] = '`' . $key . '`' . '=' . $bind_key;
                $this->_params[$bind_key] = $value;
            }
        }
        return $this;
    }

    public function delete() {
        $this->query['delete'] = 1;
        return $this;
    }

    public function where($field, $sign, $value) {
        try {
            $bind_key = ':' . $field . '_' . uniqid();

            $this->query['where'][] = '`' . $field . '` ' . $sign . ' ' . $bind_key;
            $this->_params[$bind_key] = $value;
            return $this;
        } catch (Yaf_Exception $e) {
            LogTool::mysqlerrorLog()->log($e->getMessage());
            return false;
        }
    }

    public function whereIn($field, $range) {
        if (!$field) {
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'whereIn error');
        }

        foreach ($range as $value) {
            $bind_key = ':' . $field . '_' . uniqid();

            $this->query['in'][$field][] = $bind_key;
            $this->_params[$bind_key] = $value;
        }
        return $this;
    }

    public function whereNotIn($field, $range) {
        if (!$field || !$range) {
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'whereNotIn error');
        }

        foreach ($range as $value) {
            $bind_key = ':' . $field . '_' . uniqid();

            $this->query['notIn'][$field][] = $bind_key;
            $this->_params[$bind_key] = $value;
        }
        return $this;
    }

    public function groupBy() {
        $args = func_get_args();
        if (count($args)) {
            $this->query['group'] = $args;
            return $this;
        } else {
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'group error');
        }
    }

    public function having($field, $sign, $value) {
        if ($field && $sign && $value) {
            $key = ':' . $field . '_' . uniqid();

            $this->query['having'][] = '`' . $field . '`' . $sign . $key;
            $this->_params[$key] = $value;
        } else {
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'having error');
        }
        return $this;
    }

    public function orderBy($field, $rule) {
        if ($field && $rule) {
            $this->query['order'][] = "`{$field}` " . strtoupper($rule);
        }
        return $this;
    }

    public function limit($limit, $offset = 0) {
        $offset = intval($offset);
        $offset < 0 && $offset = 0;

        $this->query['limit'] = array(
            'limit' => intval($limit),
            'offset' => $offset
        );
        return $this;
    }

    public function toSql() {
        return $this->getSQL();
    }

    public function build($original_sql = null) {
        $sql = ($original_sql == null) ? ($sql = $this->getSQL()) : $original_sql;
        $res = $this->query($sql, $this->_params);
        return $res;
    }

    public function query($sql, $param = array()) {
        if (!$sql)
            return FALSE;
        $select_sql = strpos(strtolower($sql), 'select') !== false ? true : false;
        self::$_link = (!$select_sql || $this->_connect) ? $this->getWInstance() : $this->getRInstance();
        $sth = self::$_link->prepare($sql);
        try {
            $res = empty($param) ? $sth->execute() : $sth->execute($param);
            
            if( $res && $select_sql ) {
                $res = $sth->fetchAll(PDO::FETCH_ASSOC);
            }
            if ($res && isset($this->query['insertOne'])) {
                $res = self::$_link->lastInsertId();
            }
            
            $this->recover();
            LogTool::mysqllogLog()->log($sql . json_encode($param));
            return $res;
        } catch (PDOException $e) {
            $this->recover();
            LogTool::mysqlerrorLog()->log($e->getMessage());
            throw new YExceptionTool(YExceptionTool::MYSQL_ERROR, 'query error');
        }
    }

    private function recover() {
        $this->query = array();
        $this->_params = array();
    }

    public function getErrorLog($date = '') {
        $date = $date ? date('Ymd', strtotime($date)) : date('Ymd');

        $file_name = Yaf_Registry::get("config")->application->log->toArray()['path'] . 'mysqlerror';
        $log_name = 'mysqlerror_' . $date . '.log';
        $filename = $file_name . '/' . $log_name;

        //todo test the result
        $error_info = system("tail {$filename}");
        return $error_info;
    }

    public function getSQL() {
        $sql = '';
        if (isset($this->query['select'])) {
            $fields = in_array('*', $this->query['select']) ? '*' : implode(', ', $this->query['select']);
            $sql .= 'SELECT ' . (isset($this->query['distinct']) ? 'DISTINCT ' : ' ') . $fields . ' FROM ' . $this->table . ' ';

            if (isset($this->query['where'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= 'WHERE ';
                $sql .= implode(' AND ', $this->query['where']) . ' ';
            }
            if (isset($this->query['in'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= ' WHERE ';

                foreach ($this->query['in'] as $key => $value) {
                    $sql .= '`' . $key . '` IN (' . implode(', ', $value) . ') AND ';
                }
                $sql = rtrim($sql, 'AND ');
            }
            if (isset($this->query['notIn'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= ' WHERE ';

                foreach ($this->query['notIn'] as $key => $value) {
                    $sql .= '`' . $key . '` NOT IN (' . implode(', ', $value) . ') AND ';
                }
                $sql = rtrim($sql, 'AND ');
            }
            if (isset($this->query['group'])) {
                $sql .= 'GROUP BY `' . implode('`, `', $this->query['group']) . '` ';
            }
            if (isset($this->query['having'])) {
                $sql .= 'HAVING ' . implode(' AND ', $this->query['having']) . ' ';
            }
            if (isset($this->query['order'])) {
                $sql .= 'ORDER BY ' . implode(', ', $this->query['order']) . ' ';
            }
            if (isset($this->query['limit'])) {
                $sql .= "LIMIT " . (isset($this->query['limit']['offset']) ? $this->query['limit']['offset'] . ' , ' . $this->query['limit']['limit'] : $this->query['limit']['limit']);
            }
        } elseif (isset($this->query['insertOne'])) {
            $bind = array_fill(0, count($this->query['insertOne']), '?');
            $sql .= 'INSERT INTO ' . $this->table . ' (' . implode(', ', $this->query['insertOne']) . ') VALUES (' . implode(', ', $bind) . ')';
        } elseif (isset($this->query['insertMulti'])) {
            $field_num = count($this->query['insertMulti']);
            $row_num = intval(count($this->_params) / $field_num);

            $bind_str = '(' . rtrim(str_repeat('?, ', $field_num), ', ') . ')';
            $binds = array_fill(0, $row_num, $bind_str);

            $sql .= 'INSERT INTO ' . $this->table . ' (' . implode(', ', $this->query['insertMulti']) . ') VALUES ' . implode(', ', $binds);
        } elseif (isset($this->query['update'])) {
            $sql .= 'UPDATE ' . $this->table . ' SET ' . implode(', ', $this->query['update']) . ' ';
            if (isset($this->query['where'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= 'WHERE ';
                $sql .= implode(' AND ', $this->query['where']) . ' ';
            }
            if (isset($this->query['in'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= 'WHERE ';

                foreach ($this->query['in'] as $key => $value) {
                    $sql .= $key . ' IN (' . implode(', ', $value) . ') AND ';
                }
                $sql = rtrim($sql, 'AND ');
            }
            if (isset($this->query['notIn'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= 'WHERE ';

                foreach ($this->query['notIn'] as $key => $value) {
                    $sql .= $key . ' NOT IN (' . implode(', ', $value) . ') AND ';
                }
                $sql = rtrim($sql, 'AND ');
            }
        } else if (isset($this->query['delete'])) {
            $sql .= 'DELETE FROM ' . $this->table . ' ';
            if (isset($this->query['where'])) {
                $sql .= 'WHERE ' . implode(' AND ', $this->query['where']);
            }
            if (isset($this->query['in'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= 'WHERE ';

                foreach ($this->query['in'] as $key => $value) {
                    $sql .= $key . ' IN (' . implode(', ', $value) . ') AND ';
                }
                $sql = rtrim($sql, 'AND ');
            }
            if (isset($this->query['notIn'])) {
                strpos($sql, 'WHERE') !== false ? $sql .= ' AND ' : $sql .= 'WHERE ';

                foreach ($this->query['notIn'] as $key => $value) {
                    $sql .= $key . ' NOT IN (' . implode(', ', $value) . ') AND ';
                }
                $sql = rtrim($sql, 'AND ');
            }
        }

        return $sql;
    }

}
